\CUSProvideExplLibrary{bnf}{\cus@d@te}{\cus@versi@n}{Backus-Naur Form (BNF)}

\tl_new:N \l__cus_bnf_geometry_tl
\newcounter{texbnf@internal}
\cs_set_protected_nopar:Npn \cus@mathrule 
  { \hbox{\setbox\z@\hbox{$()$}\vrule\@height\ht\z@\@depth\dp\z@\@width.1\wd\z@} }

\keys_define:nn { cus/texbnf }
  {
    format .tl_set:N = \l__cus_bnf_format_tl ,
    Nformat .tl_set:N = \l__cus_bnf_N_format_tl ,
    non-terminal-format .tl_set:N = \l__cus_bnf_N_format_tl ,
    Iformat .tl_set:N = \l__cus_bnf_I_format_tl ,
    is-format .tl_set:N = \l__cus_bnf_I_format_tl ,
    Oformat .tl_set:N = \l__cus_bnf_O_format_tl ,
    or-format .tl_set:N = \l__cus_bnf_O_format_tl ,
    Tformat .tl_set:N = \l__cus_bnf_T_format_tl ,
    terminal-format .tl_set:N = \l__cus_bnf_T_format_tl ,
    clear-all-format .code:n = 
      { Nformat = {} , Iformat = {} , Oformat = {} , Tformat = {} } ,
    clear-all-format .value_forbidden:n = true ,

    Tformat .initial:n = \ttfamily ,

    Nleft .tl_set:N = \l__cus_bnf_N_left_tl ,
    Nleft .initial:n = \ensuremath{\langle} ,
    non-terminal-left .tl_set:N = \l__cus_bnf_N_left_tl ,
    Nright .tl_set:N = \l__cus_bnf_N_right_tl ,
    Nright .initial:n = \ensuremath{\rangle} ,
    non-terminal-left .tl_set:N = \l__cus_bnf_N_right_tl ,
    N .meta:n = { Nleft = {#1} , Nright = {#1} } ,
    non-terminal .meta:n = { Nleft = {#1} , Nright = {#1} } ,
    I .tl_set:N = \l__cus_bnf_I_tl ,
    I .initial:n = \ensuremath{\longrightarrow} ,
    is .tl_set:N = \l__cus_bnf_I_tl ,
    O .tl_set:N = \l__cus_bnf_O_tl ,
    O .initial:n = \cus@mathrule ,
    or .tl_set:N = \l__cus_bnf_O_tl ,
    Tleft .tl_set:N = \l__cus_bnf_T_left_tl ,
    terminal-left .tl_set:N = \l__cus_bnf_T_left_tl ,
    Tright .tl_set:N = \l__cus_bnf_T_right_tl ,
    terminal-right .tl_set:N = \l__cus_bnf_T_right_tl ,
    T .meta:n = { Tleft = {#1} , Tright = {#1} } ,
    terminal .meta:n = { Tleft = {#1} , Tright = {#1} } ,

    label-prefix .tl_set:N = \l__cus_bnf_label_prefix_tl ,
    label-prefix .initial:n = texbnf// ,
    label-suffix .tl_set:N = \l__cus_bnf_label_suffix_tl ,
    hyper .bool_set:N = \l__cus_bnf_hyper_bool ,
    hyper-color .code:n = \tl_set:Nn \__cus_bnf_hyper_color: { \cus_color_select:n {#1} } ,
    indent .code:n = \tl_put_right:Nn \l__cus_bnf_geometry_tl { \setlength\listparindent{#1} } ,
    itemsep .code:n = \tl_put_right:Nn \l__cus_bnf_geometry_tl { \setlength\itemsep{#1} } ,
    parsep .code:n = \tl_put_right:Nn \l__cus_bnf_geometry_tl { \setlength\parsep{#1} } ,
    geometry .code:n = \tl_put_right:Nn \l__cus_bnf_geometry_tl {#1} ,
  }

\cs_set:Npn \__cus_bnf_hyper_color: { \cus@colorlink }
\cs_new_protected:Npn \__cus_bnf_hyper_anchor:n #1 
  { \cus_bnf_nonterminal_start: #1 \cus_bnf_nonterminal_stop: }
\cs_set_protected_nopar:Npn \BNFanchor { \__cus_bnf_hyper_anchor:n }
\cs_new_protected:Npn \__cus_bnf_hyper_ref:n #1 
  { \cus_bnf_nonterminal_start: #1 \cus_bnf_nonterminal_stop: }
\cs_set_protected_nopar:Npn \BNFref { \__cus_bnf_hyper_ref:n }
\cus_pkg_code:nnn { hyperref } { }
  {
    \cs_gset_protected:Npn \__cus_bnf_hyper_anchor:n #1
      {
        \cus_bnf_nonterminal_start: #1 \cus_bnf_nonterminal_stop:
        \group_begin:
        \int_gincr:N \c@texbnf@internal 
        \protected@edef \l__cus_bnf_label_tl 
          { \l__cus_bnf_label_prefix_tl #1 \l__cus_bnf_label_suffix_tl }
        \str_set:Nx \l__cus_bnf_label_tl { \tl_to_str:N \l__cus_bnf_label_tl }
        \tl_replace_all:Nnn \l__cus_bnf_label_tl { ~ } { - }
        \exp_args:NNo \tl_replace_all:Nnn \l__cus_bnf_label_tl { \c_backslash_str } { / }
        \Hy@MakeCurrentHref { texbnf. \int_use:N \c@texbnf@internal }
        \Hy@raisedlink { \hyper@anchor { \@currentHref } }
        \label { \l__cus_bnf_label_tl }
        \group_end:
      }
    \cs_gset_protected:Npn \__cus_bnf_hyper_ref:n #1
      {
        \group_begin:
        \protected@edef \l__cus_bnf_label_tl 
          { \l__cus_bnf_label_prefix_tl #1 \l__cus_bnf_label_suffix_tl }
        \str_set:Nx \l__cus_bnf_label_tl { \tl_to_str:N \l__cus_bnf_label_tl }
        \tl_replace_all:Nnn \l__cus_bnf_label_tl { ~ } { - }
        \exp_args:NNo \tl_replace_all:Nnn \l__cus_bnf_label_tl { \c_backslash_str } { / }
        \cs_if_exist:cTF { r@ \l__cus_bnf_label_tl }
          {
            \__cus_bnf_hyper_color: 
            \hyper@linkstart { texbnf } { \hyperget {anchor} { \l__cus_bnf_label_tl } } 
            { \cus_bnf_nonterminal_start: #1 \cus_bnf_nonterminal_stop: } 
            \hyper@linkend
          }
          { \cus_bnf_nonterminal_start: #1 \cus_bnf_nonterminal_stop: }
        \group_end:
      }
  }

\group_begin:
\char_set_catcode_active:N <
\char_set_catcode_active:N >
\cs_new_protected_nopar:Npn \__cus_bnf_h:w <#1>
  { 
    \mode_if_vertical:T { \BNFItem } 
    \__cus_bnf_hyper:n {#1} 
  }
\cs_new_protected_nopar:Npn \__cus_bnf_langle_h: { \__cus_bnf_h:w < }
\group_end:

\cs_new_protected:Npn \cus_bnf_start: 
  {
    \group_begin:
    \cs_set_protected:Npn \is { \ \BNFI \ }
    \cs_set_protected:Npn \alt { \ \cus_bnf_or: \ }
    \cs_set_eq:NN \h \__cus_bnf_h:w 
    \cs_set_eq:NN \V \Verbatimize 
    \bool_if:NT \l__cus_bnf_hyper_bool
      { \cs_set_eq:NN \__cus_bnf_langle: \__cus_bnf_langle_h: }
    \char_set_active_eq:NN : \__cus_bnf_colon:
    \char_set_active_eq:NN " \__cus_bnf_quot:
    \char_set_active_eq:NN | \__cus_bnf_vert:
    \char_set_active_eq:NN < \__cus_bnf_langle:
    \char_set_active_eq:NN > \__cus_bnf_rangle:
    \char_set_active_eq:NN ^ \hi 
    \char_set_active_eq:NN _ \lo 
    \char_set_catcode_active:N :
    \char_set_catcode_active:N "
    \char_set_catcode_active:N |
    \char_set_catcode_active:N <
    \char_set_catcode_active:N >
    \char_set_catcode_active:N ^
    \char_set_catcode_active:N _
    \l__cus_bnf_format_tl
  }
\cs_new_protected:Npn \__cus_bnf_colon:
  { \mode_if_math:TF { \c_colon_str } { \peek_after:Nw \__cus_bnf_colon_auxi: } }
\cs_new:Npn \__cus_bnf_colon_auxi:
  {
    \token_if_macro:NTF \l_peek_token
      { \__cus_bnf_colon_auxii:n } { \BNFI }
  }
\cs_new:Npn \__cus_bnf_colon_auxii:n #1
  {
    \token_if_active:NTF #1
      {
        \token_if_eq_charcode:NNTF #1 :
          { \peek_after:Nw \__cus_bnf_colon_auxiii: }
          { \BNFI #1 }
      }
      { #1 }
  }
\cs_new:Npn \__cus_bnf_colon_auxiii:
  {
    \token_if_other:NTF \l_peek_token
      { 
        \token_if_eq_charcode:NNTF \l_peek_token =
          { \exp_after:wN \BNFI \use_none:n }
          { \c_colon_str }
      }
      { \c_colon_str }
  }
\cs_new_protected:Npn \__cus_bnf_quot:
  { \mode_if_math:TF { " } { \cus_bnf_terminal_start: } }
\cs_new_protected:Npn \__cus_bnf_vert:
  { \mode_if_math:TF { | } { \__cus_bnf_double_it:NN | \cus_bnf_or: } }
\cs_new_protected:Npn \__cus_bnf_langle:
  {
    \mode_if_math:TF { < }
      {
        \mode_if_vertical:TF
          { \BNFItem \cus_bnf_nonterminal_start: }
          { \cus_bnf_nonterminal_start: }
      }
  }
\cs_new_protected:Npn \__cus_bnf_rangle:
  { \mode_if_math:TF { > } { \cus_bnf_nonterminal_stop: } }
\cs_new_protected:Npn \cus_bnf_stop: 
  {
    \group_end:
  }
\cs_new_protected:Npn \__cus_bnf_double_it:NN #1#2
  {
    \tl_set:Nn \l__cus_bnf_tmp_tl { #1 #2 }
    \peek_after:Nw \__cus_bnf_double_it_auxi:
  }
\cs_new:Npn \__cus_bnf_double_it_auxi:
  {
    \token_if_macro:NTF \l_peek_token
      { \__cus_bnf_double_it_auxii:n }
      { \exp_after:wN \use_ii:nn \l__cus_bnf_tmp_tl }
  }
\cs_new:Npn \__cus_bnf_double_it_auxii:n #1
  {
    \token_if_active:NTF #1
      {
        \@EAEAEA \token_if_eq_charcode:NNTF \@EA \use_i:nn \l__cus_bnf_tmp_tl #1
          { \@EAEAEA \token_to_str:N \@EA \use_i:nn \l__cus_bnf_tmp_tl }
          { \exp_after:wN \use_ii:nn \l__cus_bnf_tmp_tl #1 }
      }
      { #1 }
  }
\cs_new_protected:Npn \cus_bnf_nonterminal_start: 
  {
    \mode_leave_vertical:
    \group_begin:
    \l__cus_bnf_N_format_tl
    \l__cus_bnf_N_left_tl
    \tex_kern:D \c_zero_dim
  }
\cs_new_protected:Npn \cus_bnf_nonterminal_stop:
  {
    \tex_kern:D \c_zero_dim
    \l__cus_bnf_N_right_tl
    \group_end:
  }
\cs_new_protected:Npn \cus_bnf_nonterminal:n #1
  { \cus_bnf_nonterminal_start: #1 \cus_bnf_nonterminal_stop: }
\cs_new_protected:Npn \cus_bnf_is:
  { 
    \mode_leave_vertical:
    \group_begin: \l__cus_bnf_I_format_tl \l__cus_bnf_I_tl \group_end: 
  }
\cs_new_protected:Npn \cus_bnf_or:
  { 
    \mode_leave_vertical:
    \group_begin: \l__cus_bnf_O_format_tl \l__cus_bnf_O_tl \group_end: 
  }
\cs_new_protected:Npn \cus_bnf_terminal_start:
  {
    \mode_leave_vertical:
    \group_begin:
    \char_set_active_eq:NN " \cus_bnf_terminal_stop:
    \l__cus_bnf_T_format_tl
    \l__cus_bnf_T_left_tl
    \tex_kern:D \c_zero_dim
  }
\cs_new_protected:Npn \cus_bnf_terminal_stop:
  {
    \tex_kern:D \c_zero_dim
    \l__cus_bnf_T_right_tl 
    \group_end:
  }
\cs_new_protected:Npn \cus_bnf_terminal:n #1 
  { \cus_bnf_terminal_start: #1 \cus_bnf_terminal_stop: }
\cs_new_protected_nopar:Npn \BNFItem 
  { 
    \peek_charcode_remove:NTF *
      { 
        \item \relax 
        \cs_set_eq:NN \__cus_bnf_hyper:n \__cus_bnf_hyper_ref:n 
      }
      { 
        \item \relax 
        \cs_set_eq:NN \__cus_bnf_hyper:n \__cus_bnf_hyper_anchor:n 
      }
  }
\ekeysdeclarecmd \BNFN { p{*+} }
  {
    \bool_if:NTF \l__cus_bnf_hyper_bool 
      {
        \NumberCase {#1}
          {
            { \__cus_bnf_hyper_ref:n }
            { \cus_peek_verbatim:nn \__cus_bnf_hyper_ref:n }
            { \cus_peek_verbatim:nw \__cus_bnf_hyper_ref:n }
          } { }
      }
      {
        \NumberCase {#1}
          {
            { \cus_bnf_nonterminal:n }
            { \cus_peek_verbatim:nn \cus_bnf_nonterminal:n }
            { \cus_peek_verbatim:nw \cus_bnf_nonterminal:n }
          } { }
      }
  }
\cs_new_protected_nopar:Npn \BNFI 
  { 
    \cus_bnf_is: 
    \cs_set_eq:NN \__cus_bnf_hyper:n \__cus_bnf_hyper_ref:n 
  }
\cs_new_protected_nopar:Npn \BNFO { \cus_bnf_or: }
\ekeysdeclarecmd \BNFT { p{*+} }
  {
    \NumberCase {#1}
      {
        { \cus_bnf_terminal:n }
        { \cus_peek_verbatim:nn { \cus_bnf_terminal:n } }
        { \cus_peek_verbatim:nw { \cus_bnf_terminal:n } }
      } { }
  }

\NewDocumentEnvironment { latexbnf } { !+O{} }
  {
    \keys_set:nn { cus/texbnf } {#1}
    \cus_bnf_start: \obeylines 
    \list { } 
      { 
        \skip_zero:N \partopsep
        \skip_zero:N \parsep 
        \skip_zero:N \itemsep 
        \skip_zero:N \topskip 
        \skip_zero:N \parskip 
        \dim_set_eq:NN \leftmargin \c_zero_dim 
        \dim_set:Nn \rightmargin \c_zero_dim 
        \dim_set:Nn \listparindent { 2em }
        \l__cus_bnf_geometry_tl
      }
  }
  { \endlist \cus_bnf_stop: }
